﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Editors.WCF;
using System.CodeDom;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell.Interop;

namespace Structura.WCF.StructuraWcfProxyGenerator
{
   [GuidAttribute("64205D39-7D51-4c6d-8C0F-237E6FE2BD70")]
   public class StructuraWcfProxyGenerator : WCFProxyGenerator
   {
      protected override void CallCodeGeneratorExtensions(CodeCompileUnit compileUnit)
      {
         //todo: we can implement the mapping of validation attributes using an external xml file placed inside the current project
         //      this way we can use the annotation objects even with wcf services
         //EnvDTE.DTE vs = (EnvDTE.DTE)this.ServiceProvider.GetService(typeof(EnvDTE.DTE));
         //EnvDTE.Project prj = vs.SelectedItems.Item(1).ProjectItem.ContainingProject;
         //System.Diagnostics.Debug.WriteLine(prj.FullName);

         base.CallCodeGeneratorExtensions(compileUnit);

         // find all classes that inherit from ClientBase (all proxies)
         var proxies = FindAllProxyClasses(compileUnit);
         // add impersonation code to their constructors
         foreach (CodeTypeDeclaration proxy in proxies)
         {
            AddPartialMethods(proxy);
            AddValidationToProperties(proxy);
         }
      }

      protected virtual CodeTypeDeclarationCollection FindAllProxyClasses(CodeCompileUnit compileUnit)
      {
         CodeTypeDeclarationCollection result = new CodeTypeDeclarationCollection();

         // search for all the proxy class (the one that inherits from ClientBase)
         foreach (CodeNamespace ns in compileUnit.Namespaces)
         {
            foreach (CodeTypeDeclaration type in ns.Types)
            {
               // does this type inherit from ClientBase?
               if (type.IsClass && type.IsPartial)
               {
                  foreach (CodeTypeReference baseType in type.BaseTypes)
                  {
                     // if (baseType.BaseType == "System.Object")
							// we need to take into account even model classes derived from other classes
                     if ((!IsInterface(baseType)) &&
								 (baseType.BaseType != "System.ComponentModel.AsyncCompletedEventArgs") && 
							    (!baseType.BaseType.Contains("System.ServiceModel.ClientBase")))
                     {
                        // we have found the proxy!
                        result.Add(type);
                        break;
                     }
                  }
               }
            }
         }
         return result;
      }

   	private static bool IsInterface(CodeTypeReference reference)
   	{
   		// try to create the type and see if it's an interface
   		try
   		{
   			return Type.GetType(reference.BaseType).IsInterface;
   		}
   		catch (Exception)
   		{
   			return false;
   		}
   	}

   	protected virtual void AddPartialMethods(CodeTypeDeclaration type)
      {
         IVsSingleFileGenerator ivs = (IVsSingleFileGenerator)this;
         // ugly, but it's the only way I found to identify the language used
         string ext;
         ivs.DefaultExtension(out ext);
         CodeSnippetTypeMember literalMember;
         if (ext.Contains("cs"))
         {
            // csharp
            literalMember = new CodeSnippetTypeMember(
               "partial void ValidateProperty(string propertyName, object value);");
         }
         else
         {
            // vb 
            literalMember = new CodeSnippetTypeMember(
               "Partial Sub ValidateProperty(byval propertyName as String, byval value as Object)");
         }
         type.Members.Add(literalMember);

         // the codedom do not support partial methods yet
         //CodeMemberMethod MyMethod = new CodeMemberMethod();
         //MyMethod.Name = "ValidateProperty";
         //MyMethod.ReturnType = new CodeTypeReference("partial void");
         //MyMethod.Attributes = MemberAttributes.ScopeMask;
         //MyMethod.Parameters.Add(new CodeParameterDeclarationExpression("System.String", "propertyName"));
         //MyMethod.Parameters.Add(new CodeParameterDeclarationExpression("System.Object", "value"));
         //type.Members.Add(MyMethod);
      }

      protected virtual void AddValidationToProperties(CodeTypeDeclaration type)
      {
         foreach (CodeTypeMember member in type.Members)
         {
            CodeMemberProperty ctor = member as CodeMemberProperty;
            if (ctor != null)
            {
               // create a code statement like:
               // this.ValidateProperty("Title", value)
               CodeMethodInvokeExpression method = new CodeMethodInvokeExpression(
                   new CodeThisReferenceExpression(),
                   "ValidateProperty",
                  new CodeExpression[] { 
                     new CodePrimitiveExpression(ctor.Name), 
                     new CodePropertySetValueReferenceExpression() 
                  });

               // we got a constructor
               ctor.SetStatements.Insert(0, new CodeExpressionStatement(method));
            }
         }
      }
   }
}
